Перейти к основному содержимому

4.08. Абстракция

Разработчику Аналитику Тестировщику
Архитектору Инженеру

Абстракция

Абстракция – создание абстракных моделей объектов, которые скрывают внутреннюю реализацию. Позволяет работать с объектами, не вникая в их внутреннее устройство. Например, водитель использует педаль газа, не зная, как устроен двигатель.

Суть в том, чтобы скрыть сложные детали реализации и показать только важные свойства и функции объекта.


Зачем нужна абстракция?

  1. Упрощение сложных систем. Можно разбить сложные системы на понятные части. К примеру, мы в реальном мире не думаем о том, как работает двигатель автомобиля, чтобы управлять, нам хватит понимать, где педали, а где руль. В программировании можно так же спрятать внутрь реализацию.
  2. Сокрытие ненужных деталей. К примеру, другому программисту не нужно знать, как работает метод «мяукнуть()», ему достаточно лишь понимать, что он делает. Понятно, что мяукание издаст определённый звук. Но какие будут происходить процессы внутри «кота» - не нужно нам знать.
  3. Уменьшение сложности кода. Абстракция помогает структурировать код, делая его:
    • читаемым (логика разбита на модули);
    • поддерживаемым (изменения внутри класса не ломают внешний код);
    • масштабируемым (можно добавлять новые функции, не переписывая всё).
  4. Унификация взаимодействия. Абстракция позволяет стандартизировать работу с объектами. Допустим, в игре разные персонажи могут иметь метод attack(), но реализация у мечника и мага будет отличаться.

Абстракция достигается за счёт:

  • абстрактных классов;
  • абстрактных методов;
  • сокрытия данных.

image-4.png


Абстрактный класс – это класс, который нельзя создать напрямую – только через наследование. Содержит абстрактные методы и/или обычные методы.

Абстрактный класс нужен для того, чтобы задать общую структуру для дочерних классов и запретить создание объектов «неполного» типа.

Нельзя создавать экземпляр абстрактного класса, но если сделать неабстрактного наследника, то можно уже создавать объекты.

Пример на Java:

// Абстрактный класс "Транспорт"
abstract class Transport {
private String name; // поле

public Transport(String name) { // конструктор
this.name = name;
}

// Абстрактный метод (без тела)
public abstract void move();

// Обычный метод
public void honk() {
System.out.println(name + " сигналит: Би-бип!");
}
}

// Класс-наследник "Машина"
class Car extends Transport {
public Car(String name) {
super(name); // вызываем конструктор родителя
}

// Реализация абстрактного метода
@Override
public void move() {
System.out.println(getClass().getSimpleName() + " едет по дороге!");
}
}

// Класс-наследник "Самолет"
class Airplane extends Transport {
public Airplane(String name) {
super(name);
}

@Override
public void move() {
System.out.println(getClass().getSimpleName() + " летит в небе!");
}
}

public class Main {
public static void main(String[] args) {
Transport car = new Car("Toyota");
Transport airplane = new Airplane("Boeing 747");

car.move(); // Вывод: "Car едет по дороге!"
car.honk(); // Вывод: "Toyota сигналит: Би-бип!"

airplane.move(); // Вывод: "Airplane летит в небе!"
airplane.honk(); // Вывод: "Boeing 747 сигналит: Би-бип!"
}
}

Transport – абстрактный класс с:

  • абстрактным методом move();
  • обычным методом honk().

Car и Airplane – наследники, которые обязаны реализовать move().

И нельзя создать Transport напрямую:

Transport t = new Transport();  // Ошибка! Transport абстрактный.

Абстрактный метод – это метод без реализации, который обязаны переопределить все дочерние классы. Принцип тот же – это просто указание, что абстрактный класс содержит этот метод, но как работает метод – не указано. Так, программист реализует критичную логику, и использует унифицированный интерфейс для разных классов. Словом, будет порядок. Пример:

abstract class Animal {
abstract void makeSound(); // абстрактный метод
}

class Dog extends Animal {
void makeSound() { // обязательная реализация
System.out.println("Гав!");
}
}

Интерфейсы – полностью абстрактный «контракт», где все методы абстрактные. В отличие от абстрактного класса, в интерфейсе нет полей и реализованных методов, и класс может реализовать много интерфейсов, но наследовать только один абстрактный класс. Пример:

interface Flyable {
void fly(); // абстрактный метод
}

class Bird implements Flyable {
public void fly() {
System.out.println("Лечу как птица!");
}
}

Ещё один пример интерфейса:

// Интерфейс "Ремонтируемый"
interface Repairable {
void repair();
}

// Абстрактный класс + интерфейс
abstract class Transport implements Repairable {
public abstract void move();

// Общий метод для всех наследников
public void startEngine() {
System.out.println("Двигатель запущен.");
}
}

class Bicycle extends Transport {
@Override
public void move() {
System.out.println("Велосипед едет!");
}

@Override
public void repair() {
System.out.println("Чиним цепь...");
}
}

public class Main {
public static void main(String[] args) {
Bicycle bike = new Bicycle();
bike.startEngine(); // Вывод: "Двигатель запущен." (наследовано)
bike.move(); // Вывод: "Велосипед едет!"
bike.repair(); // Вывод: "Чиним цепь..."
}
}

Интерфейс – только контракт (все методы абстрактные).

Абстрактный класс – частичная реализация + контракт.

Абстрактные классы особенно полезны в библиотеках и фреймворках, когда надо спрятать внутри всю логику и дать пользователю инструмент.

Таким образом,

  • «Что» – это публичный интерфейс (какие методы доступны пользователю);
  • «Как» - скрытая реализация (как эти методы работают внутри).

Где применяется абстракция – примеры:

  1. Библиотеки и фреймворки, к примеру, в ORM вы работаете с моделями вроде Model.save(), не зная самих SQL-запросов.
  2. Игры – абстрактный класс Enemy (враг) с методом attack(), где Dragon и Zombie реализуют свою логику атаки.
  3. GUI – кнопка Button.click() скрывает детали отрисовки и обработки событий.
  4. API – например, оплата идёт путем вызова метода, без знания протоколов.